/******************************************************************
*
*	CyberHTTP for Java
*
*	Copyright (C) Satoshi Konno 2002-2004
*
*	File: HTTPConnection.java
*
*	Revision;
*
*	11/18/02
*		- first revision.
*	09/02/03
*		- Giordano Sassaroli <sassarol@cefriel.it>
*		- Problem : The API is unable to receive responses from the Microsoft UPnP stack
*		- Error : the Microsoft UPnP stack is based on ISAPI on IIS, and whenever IIS
*                 receives a post request, it answers with two responses: the first one has no 
*		          body and it is a code 100 (continue) response, which has to be ignored. The
*		          second response is the actual one and should be parsed as the response.
*	02/09/04
*		- Ralf G. R. Bergs" <Ralf@Ber.gs>
*		- Why do you strip leading and trailing white space from the response body?
*		- Disabled to trim the content string.
*	03/11/04
*		- Added some methods about InputStream content.
*		  setContentInputStream(), getContentInputStream() and hasContentInputStream().
*	03/16/04
*		- Thanks for Darrell Young
*		- Added setVersion() and getVersion();
*	03/17/04
*		- Added hasFirstLine();
*	05/26/04
*		- Jan Newmarch <jan.newmarch@infotech.monash.edu.au> (05/26/04)
*		- Changed setCacheControl() and getChcheControl();
*
*******************************************************************/

package org.cybergarage.http;

import java.io.*;
import java.util.*;
import java.util.logging.Logger;

import org.cybergarage.net.*;
import org.cybergarage.util.*;
import java.util.Calendar;

import com.cidero.util.UTF8Util;

public class HTTPPacket 
{
  private static Logger logger = Logger.getLogger("org.cybergarage.http");
  
	////////////////////////////////////////////////
	//	Constructor
	////////////////////////////////////////////////
	
	public HTTPPacket()
	{
		setVersion(HTTP.VERSION);
		setContentInputStream(null);
	}

	public HTTPPacket(HTTPPacket httpPacket)
	{
		setVersion(HTTP.VERSION);
		set(httpPacket);
		setContentInputStream(null);
	}

	public HTTPPacket(InputStream in)
	{
		setVersion(HTTP.VERSION);
		set(in);
		setContentInputStream(null);
	}

	////////////////////////////////////////////////
	//	Version
	////////////////////////////////////////////////
	
	private String version;
	
	public void setVersion(String ver)
	{
		version = ver;
	}
	
	public String getVersion()
	{
		return version;
	}
	
	/**
   * Read packet from input stream
   *
   * @param inStream  Input stream
   *
   * @note HTTP is mix of text (headers) and binary data (content), so need to read
   * data in binary mode (Can't use BufferedReader to read headers)
   */
	
	protected void set(InputStream inStream)
	{
 		try {
      //			BufferedReader reader =
      //        new BufferedReader(new InputStreamReader(in, "UTF-8" ));
			InputStreamReader reader = new InputStreamReader(inStream, "UTF-8" );
			
			String firstLine = readLine( inStream );
			setFirstLine(firstLine);
			
			// Thanks for Giordano Sassaroli <sassarol@cefriel.it> (09/03/03)
			HTTPStatus httpStatus = new HTTPStatus(firstLine);
			int statCode = httpStatus.getStatusCode();
			if (statCode == HTTPStatus.CONTINUE){
				//ad hoc code for managing iis non-standard behaviour
				//iis sends 100 code response and a 200 code response in the same
				//stream, so the code should check the presence of the actual
				//response in the stream.
				//skip all header lines
				String headerLine = readLine( inStream );
				while ((headerLine != null) && (0 < headerLine.length()) ) {
					HTTPHeader header = new HTTPHeader(headerLine);
					if (header.hasName() == true)
						setHeader(header);
					headerLine = readLine( inStream );
				}
				//look forward another first line
				String actualFirstLine = readLine( inStream );
				if ((actualFirstLine != null) && (0 < actualFirstLine.length()) ) {
					//this is the actual first line
					setFirstLine(actualFirstLine);
				}else{
					return;
				}
			}
			
      // OJN Hack to handle HTTP packets with missing CONTENT-LENGTH 
      // headers - assume big packet
			long conLen = 999999999;

			String headerLine = readLine( inStream );
			while ((headerLine != null) && (0 < headerLine.length()) )
      {
				HTTPHeader header = new HTTPHeader(headerLine);
				if (header.hasName() == true)
        {
					setHeader(header);

          // OJN Hack
          if( header.getName().equalsIgnoreCase( HTTP.CONTENT_LENGTH ) )
            conLen = getContentLength();
        }
        
				headerLine = readLine( inStream );
			}
			
      // If OJN Hack enabled, take this out
			//long conLen = getContentLength();
      if( conLen == 999999999 )
      {
        // If this is not an HTTP response, (HEAD/GET) it doesn't require a 
        // Content-Length header
        String firstLineTmp = getFirstLine();
        if( (firstLineTmp == null) || (! firstLineTmp.startsWith("HTTP")) )
          return;
      }
      
      //logger.finest("conLen = " + conLen );
      
			if (0 < conLen)
      {
				int chunkSize = HTTP.getChunkSize();
        //logger.finest("HTTP chunkSize = " + chunkSize );

        byte[] readBuf = new byte[chunkSize];
				//char readBuf[] = new char[chunkSize];
				long readCnt = 0;
				StringBuffer conBuf = new StringBuffer();

				while (readCnt < conLen)
        {
					try {
						//int len = reader.read(readBuf);
						int len = inStream.read(readBuf, 0, chunkSize);
						if (len < 0)
							break;

            // Convert raw byte array to UTF-8 string
            String tmp = new String(readBuf, 0, len, "UTF-8");
						conBuf.append( tmp );
						readCnt += len;
            //logger.finest("\n--------------- read len = " + len + "  readCnt: " + readCnt );
            //logger.finest("tmp = [" + tmp + "]" );
					}
					catch (Exception e)
					{
						logger.warning("Exception reading HTTP response" + e);
						break;
					}
				}
				// Thanks for Ralf G. R. Bergs" <Ralf@Ber.gs>
				String conStr = conBuf.toString();

        //logger.info(" ^^^^^^^^^^^Checking incoming HTTP packet for UTF8");
        //UTF8Util.checkForMultiByteChars( conStr );

				//String trimStr = conStr.trim();
				//setContent(trimStr.getBytes());
				setContent(conStr.getBytes("UTF-8"));

        // OJN HACK debugging
        //  if( conLen == 999999999 )
        //    System.out.println("**********ContentLength after read = " + getContentLength() + " ********************" );
			}

		}
		catch (Exception e) {
			Debug.warning(e);
		}
	}

	protected void set(HTTPSocket httpSock)
	{
		set(httpSock.getInputStream());
	}

	protected void set(HTTPPacket httpPacket)
	{
		setFirstLine(httpPacket.getFirstLine());
		
		clearHeaders();
		int nHeaders = httpPacket.getNHeaders();
		for (int n=0; n<nHeaders; n++) {
			HTTPHeader header = httpPacket.getHeader(n);
			addHeader(header);
		}
		setContent(httpPacket.getContent());
	}


  /**
   *  Read a line of text (HTTP header line) terminated by <LF> or <CR><LF>
   *  <CR> and <LF> characters are stripped out from the returned string
   *  Returns null at end of stream
   */
  private String readLine( InputStream inStream )
  {
    StringBuffer sb = new StringBuffer("");

    try {
      while (true)
      {
        int c = inStream.read();
        if (c == -1) { // end of stream
          return null;
        }
        else if (c == '\n') {
          break;
        }
        else if (c == '\r') {
        }
        else {
          sb.append((char) c);
        }
      }
    }
		catch (IOException e) {
			return null;
		}

    return sb.toString();
  }
  

	////////////////////////////////////////////////
	//	String
	////////////////////////////////////////////////

	private String firstLine = "";
	
	private void setFirstLine(String value)
	{
			firstLine = value;
	}
	
	protected String getFirstLine()
	{
		return firstLine;
	}

	protected String getFirstLineToken(int num)
	{
		StringTokenizer st = new StringTokenizer(firstLine, HTTP.REQEST_LINE_DELIM);
		String lastToken = "";
		for (int n=0; n<=num; n++) {
			if (st.hasMoreTokens() == false)
				return "";
			lastToken = st.nextToken();
		}
		return lastToken;
     }
	
	public boolean hasFirstLine()
	{
		return (0 < firstLine.length()) ? true : false;
	}
	
	////////////////////////////////////////////////
	//	Header
	////////////////////////////////////////////////

	private Vector httpHeaderList = new Vector();
	
	public int getNHeaders()
	{
		return httpHeaderList.size();
	}

	public void addHeader(HTTPHeader header)
	{
		httpHeaderList.add(header);
	}

	public void addHeader(String name, String value)
	{
		HTTPHeader header = new HTTPHeader(name, value);
		httpHeaderList.add(header);
	}

	public HTTPHeader getHeader(int n)
	{
		return (HTTPHeader)httpHeaderList.get(n);
	}
	
	public HTTPHeader getHeader(String name)
	{
		int nHeaders = getNHeaders();
		for (int n=0; n<nHeaders; n++) {
			HTTPHeader header = getHeader(n);
			String headerName = header.getName();
			if (headerName.equalsIgnoreCase(name) == true)
				return header;			
		}
		return null;
	}

	public void clearHeaders()
	{
		httpHeaderList.clear();
		httpHeaderList = new Vector();
	}
	
	public boolean hasHeader(String name)
	{
		return (getHeader(name) != null) ? true : false;
	}

	public void setHeader(String name, String value)
	{
		HTTPHeader header = getHeader(name);
		if (header != null) {
			header.setValue(value);
			return;
		}
		addHeader(name, value);
	}

	public void setHeader(String name, int value)
	{
		setHeader(name, Integer.toString(value));
	}

	public void setHeader(String name, long value)
	{
		setHeader(name, Long.toString(value));
	}
	
	public void setHeader(HTTPHeader header)
	{
		setHeader(header.getName(), header.getValue());
	}

	public String getHeaderValue(String name)
	{
		HTTPHeader header = getHeader(name);
		if (header == null)
			return "";
		return header.getValue();
	}

	////////////////////////////////////////////////
	// set*Value
	////////////////////////////////////////////////

	public void setStringHeader(String name, String value, String startWidth, String endWidth)
	{
		String headerValue = value;
		if (headerValue.startsWith(startWidth) == false)
			headerValue = startWidth + headerValue;
		if (headerValue.endsWith(endWidth) == false)
			headerValue = headerValue + endWidth;
		setHeader(name, headerValue);
	}

	public void setStringHeader(String name, String value)
	{
		setStringHeader(name, value, "\"", "\"");
	}
	
	public String getStringHeaderValue(String name, String startWidth, String endWidth)
	{
		String headerValue = getHeaderValue(name);
		if (headerValue.startsWith(startWidth) == true)
			headerValue = headerValue.substring(1, headerValue.length());
		if (headerValue.endsWith(endWidth) == true)
			headerValue = headerValue.substring(0, headerValue.length()-1);
		return headerValue;
	}
	
	public String getStringHeaderValue(String name)
	{
		return getStringHeaderValue(name, "\"", "\"");
	}

	public void setIntegerHeader(String name, int value)
	{
		setHeader(name, Integer.toString(value));
	}
	
	public void setLongHeader(String name, long value)
	{
		setHeader(name, Long.toString(value));
	}
	
	public int getIntegerHeaderValue(String name)
	{
		HTTPHeader header = getHeader(name);
		if (header == null)
			return 0;
		return StringUtil.toInteger(header.getValue());
	}

	public long getLongHeaderValue(String name)
	{
		HTTPHeader header = getHeader(name);
		if (header == null)
			return 0;
		return StringUtil.toLong(header.getValue());
	}

	////////////////////////////////////////////////
	//	getHeader
	////////////////////////////////////////////////
	
	public String getHeaderString()
	{
		StringBuffer str = new StringBuffer();
	
		int nHeaders = getNHeaders();
		for (int n=0; n<nHeaders; n++) {
			HTTPHeader header = getHeader(n);
			str.append(header.getName() + ": " + header.getValue() + HTTP.CRLF);
		}
		
		return str.toString();
	}

	////////////////////////////////////////////////
	//	Contents
	////////////////////////////////////////////////

	private byte content[] = new byte[0];
	
	public void setContent(byte data[])
	{
		content = data;
		setContentLength(data.length);
	}

	public void setContent(String data)
	{
    try
    {
      setContent(data.getBytes("UTF-8"));
    }
    catch( UnsupportedEncodingException e )
    {
      logger.warning("Exception " + e );
    }
	}

	public  byte[] getContent()
	{
		return content;
	}

	public  String getContentString()
	{
    try
    {
      return new String(content, "UTF-8" );
    }
    catch( UnsupportedEncodingException e )
    {
      logger.warning("Exception " + e );
      return null;
    }
	}
	
	public boolean hasContent()
	{
		return (content.length > 0) ? true : false;
	}

	////////////////////////////////////////////////
	//	Contents (InputStream)
	////////////////////////////////////////////////

	private InputStream contentInput = null;
	
	public void setContentInputStream(InputStream in)
	{
		contentInput = in;
	}

	public InputStream getContentInputStream()
	{
		return contentInput;
	}

	public boolean hasContentInputStream()
	{
		return (contentInput != null) ? true : false;
	}

	////////////////////////////////////////////////
	//	ContentType
	////////////////////////////////////////////////

	public void setContentType(String type)
	{
		setHeader(HTTP.CONTENT_TYPE, type);
	}

	public String getContentType()
	{
		return getHeaderValue(HTTP.CONTENT_TYPE);
	}

	////////////////////////////////////////////////
	//	ContentLength
	////////////////////////////////////////////////

	public void setContentLength(long len)
	{
		setLongHeader(HTTP.CONTENT_LENGTH, len);
	}

	public long getContentLength()
	{
		return getLongHeaderValue(HTTP.CONTENT_LENGTH);
	}

	/**
   *  Connection Type
   *
   *  @param connection   Typically 'close' or 'keep-alive'
   * 
   *  'close' *should* be the default behavior for HTTP 1.0
   */
	public void setConnection(String connection)
	{
		setHeader(HTTP.CONNECTION, connection);
	}

	public String getConnection()
	{
		return getHeaderValue(HTTP.CONNECTION);
	}
  

	////////////////////////////////////////////////
	//	CacheControl
	////////////////////////////////////////////////

	public void setCacheControl(String directive)
	{
		setHeader(HTTP.CACHE_CONTROL, directive);
	}
	
	public void setCacheControl(String directive, int value)
	{
		String strVal = directive + "=" + Integer.toString(value);
		setHeader(HTTP.CACHE_CONTROL, strVal);
	}
	
	public void setCacheControl(int value)
	{
		setCacheControl(HTTP.MAX_AGE, value);
	}

	public String getCacheControl()
	{
		return getHeaderValue(HTTP.CACHE_CONTROL);
	}

	////////////////////////////////////////////////
	//	Server
	////////////////////////////////////////////////

	public void setServer(String name)
	{
		setHeader(HTTP.SERVER, name);
	}

	public String getServer()
	{
		return getHeaderValue(HTTP.SERVER);
	}

	////////////////////////////////////////////////
	//	Server
	////////////////////////////////////////////////

	public void setHost(String host, int port)
	{
		String hostAddr = host;
		if (HostInterface.isIPv6Address(host) == true)
			hostAddr = "[" + host + "]";
		setHeader(HTTP.HOST, hostAddr + ":" + Integer.toString(port));
	}

	public String getHost()
	{
		return getHeaderValue(HTTP.HOST);
	}


	////////////////////////////////////////////////
	//	Server
	////////////////////////////////////////////////

	public void setDate(Calendar cal)
	{
		Date date = new Date(cal);
		setHeader(HTTP.DATE, date.getDateString());
	}

	public String getDate()
	{
		return getHeaderValue(HTTP.DATE);
	}

	////////////////////////////////////////////////
	//	set
	////////////////////////////////////////////////

/*
	public final static boolean parse(HTTPPacket httpPacket, InputStream in)
	{
 		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(in));
			return parse(httpPacket, reader);
		}
		catch (Exception e) {
			Debug.warning(e);
		}
		return false;
	}
*/
}

